<?php
declare(strict_types=1);

namespace WebApp\lib\db\mysql;

use ErrorException;
use PDO;
use PDOStatement;
use WebApp\lib\db\IDriver;
use WebApp\lib\util\Util;

/**
 * Class MysqlTable
 * @package WebApp\lib\db\mysql
 */
abstract class MysqlDriver implements IDriver
{
    const DESC = 0;
    const ASC = 1;

    protected $last_sql = '';
    protected $last_params = [];
    protected $last_id = '';
    protected $last_err_info = '';
    protected $last_err_code = '';

    /**
     * @var IMysqlConfig
     */
    public $config;
    public function __construct(IMysqlConfig $config)
    {
        $this->config =  $config;
    }

    /**
     * @param PDOStatement $stmt
     * @param $data
     */
    static protected function bindParams($stmt, $data)
    {
        if (is_array($data)) {
            foreach ($data as $k => $v) {
                if (is_array($v)) {
                    foreach ($v as $key => $val) {
                        if (is_numeric($val)) {
                            $stmt->bindValue($key, $val, PDO::PARAM_INT);
                        } else if (is_bool($val)) {
                            $stmt->bindValue($key, $val, PDO::PARAM_BOOL);
                        } else if (is_null($val)) {
                            $stmt->bindValue($key, $val, PDO::PARAM_NULL);
                        } else {
                            $stmt->bindValue($key, $val, PDO::PARAM_STR);
                        }
                    }
                } else {
                    if (is_numeric($v)) {
                        $stmt->bindValue($k, $v, PDO::PARAM_INT);
                    } else if (is_bool($v)) {
                        $stmt->bindValue($k, $v, PDO::PARAM_BOOL);
                    } else if (is_null($v)) {
                        $stmt->bindValue($k, $v, PDO::PARAM_NULL);
                    } else {
                        $stmt->bindValue($k, $v, PDO::PARAM_STR);
                    }
                }
            }
        }
    }

    static private function getBindParamFlag(&$count) {
        $num = $count % 26;
        $flag = ":" . chr(65 + (int)floor($count / 26)) . chr($num + 65);
        $count++;
        return $flag;
    }

    /**
     * @param $k
     * @param $v
     * @param $count
     * @param $this_tmp
     * @param $where
     * @param string $end
     * @throws ErrorException
     */
    static private function whereAdd($k, $v, &$count, &$this_tmp, &$where, $end = "") {
        $k_c = self::addQuote($k);

        if (is_array($v)) {
            if (is_array($v[0])) {
                foreach ($v as $item) {
                    $symbol = $item[0];
                    $value = $item[1];
                    $tmp = self::getBindParamFlag($count);
                    $where[$tmp] = $value;
                    $this_tmp .= "$k_c $symbol $tmp OR ";
                }
                $this_tmp = substr($this_tmp, 0, -3);

            } else {
                $symbol = $v[0];

                switch ($symbol)
                {
                    case "is not":
                        $this_tmp .= "$k_c $symbol null ";
                        break;
                    case "in":
                    case "not in":
                        $tmp_arr = $v[1];
                        if (!$tmp_arr) {
                            throw new ErrorException("make where error");
                        }
                        if (!is_numeric($tmp_arr[0])) {
                            foreach ($tmp_arr as &$tmp_arr_item) {
                                $tmp_arr_item = "'$tmp_arr_item'";
                            }
                        }

                        $tmp = "(" . join(",", $tmp_arr) . ")";
                        $this_tmp .= "$k_c $symbol $tmp ";
                        break;
                    case "between":
                        if (is_array($v[1])) {
                            if (is_numeric($v[1][0])) {
                                $and = "{$v[1][0]} and {$v[1][1]}";
                            } else {
                                $and = "'{$v[1][0]}' and '{$v[1][1]}'";
                            }
                            $this_tmp .= "$k_c $symbol $and ";
                        } else {
                            $this_tmp .= "$k_c $symbol $v[1] ";
                        }
                        break;
                    case 'range':
                        $this_tmp .= "$v[1] ";
                        break;
                    default:
                        $value = $v[1];
                        $tmp = self::getBindParamFlag($count);
                        $where[$tmp] = $value;
                        $this_tmp .= "$k_c $symbol $tmp ";
                        break;
                }
            }
        } else {
            $symbol = '=';
            $value = $v;
            $tmp = self::getBindParamFlag($count);
            $where[$tmp] = $value;
            $this_tmp .= "$k_c $symbol $tmp ";
        }

        if ($end) {
            $this_tmp .= "$end ";
        }
    }

    /**
     * @param $where
     * @return mixed
     * @throws ErrorException
     */
    static public function makeWhere(&$where)
    {
        $count = 0;
        if (is_array($where) && $where) {
            $str = 'WHERE ';
            foreach ($where as $k => $v) {
                $this_tmp = "";

                if ($k === "or") {
                    $k_1 = '';
                    foreach ($v as $k_1 => $v_1) {
                        break;
                    }

                    if (is_numeric($k_1)) {
                        $this_tmp .= "(";
                        foreach ($v as $v_1) {
                            $this_tmp .= "(";
                            foreach ($v_1 as $k_2 => $v_2) {
                                self::whereAdd($k_2, $v_2, $count, $this_tmp, $where, "AND");
                            }
                            $this_tmp = substr($this_tmp, 0, -4);
                            $this_tmp .= ") OR ";
                        }
                        $this_tmp = substr($this_tmp, 0, -3);
                        $this_tmp .= ")";
                    } else {
                        $this_tmp .= "(";
                        foreach ($v as $k_1 => $v_1) {
                            self::whereAdd($k_1, $v_1, $count, $this_tmp, $where, "OR");
                        }
                        $this_tmp = substr($this_tmp, 0, -3);
                        $this_tmp .= ")";
                    }
                } else {
                    self::whereAdd($k, $v, $count,$this_tmp, $where);
                }

                $str .= "($this_tmp) AND ";
                unset($where[$k]);
            }
            $str = substr($str, 0, -5);
            return $str;
        } else {
            return '';
        }
    }

    static private function addQuote($value) {
        if (Util::hasString($value, '.')) {
            $k_arr = explode('.', $value);
            $k_c = "{$k_arr[0]}.`{$k_arr[1]}`";
        } else {
            $k_c = "`$value`";
        }

        return $k_c;
    }

    /**
     * @param $sort
     * @return string
     */
    static public function makeOrder(&$sort)
    {
        $make_order = "";
        if ($sort) {
            if (is_array($sort)) {
                if (is_array($sort[0])) {
                    $order = "ORDER BY ";
                    foreach ($sort as $item) {
                        if ($item[1] == 0) {
                            $a = "DESC";
                        } else {
                            $a = "ASC";
                        }

                        $t_v = self::addQuote($item[0]);
                        $order .= "$t_v $a,";
                    }
                    $make_order = substr($order, 0, -1);
                } else {
                    $sort = array_values($sort);
                    $t_v = self::addQuote($sort[0]);
                    $make_order = "ORDER BY $t_v ASC";
                }
            } else {
                $t_v = self::addQuote($sort);
                $make_order = "ORDER BY $t_v DESC";
            }
        }
        return $make_order;
    }

    /**
     * @param $data
     * @return mixed
     */
    static public function makeSetData(&$data)
    {
        $count = 65;
        if (is_object($data)) {
            $data = json_decode(json_encode($data), true);
        }
        if ($data && is_array($data)) {
            $str = 'SET ';
            foreach ($data as $k => $v) {
                $tmp = ":b" . chr($count++);
                $str .= "`{$k}`=$tmp,";
                $data[$tmp] = $v;
                unset($data[$k]);
            }
            $str = substr($str, 0, -1);
            return [$str, $data];
        } else {
            return '';
        }
    }

    /**
     * @param $group
     * @return string
     */
    static public function makeGroup($group)
    {
        if ($group) {
            if (is_array($group)) {
                return "GROUP BY $group[0], $group[1]";
            } else {
                return "GROUP BY $group";
            }
        } else {
            return '';
        }
    }

    /**
     * @param $limit
     * @return string
     */
    static protected function makeLimit(&$limit)
    {
        if ($limit && is_array($limit)) {
            return "LIMIT " . $limit[0] . "," . $limit[1];
        } else {
            if (is_numeric($limit)) {
                return "LIMIT $limit";
            }
        }
        return '';
    }

    static protected function makeLimitV1(array &$limit)
    {
        if ($limit && is_array($limit)) {
            return "LIMIT " . $limit[0] . "," . $limit[1];
        } else {
            if (is_numeric($limit)) {
                return "LIMIT $limit";
            }
        }
        return '';
    }

    static protected function makeNeed(&$need)
    {
        if ($need) {
            if (is_object($need)) {
                return self::makeNeedV1($need);
            } else {
                $str = '';
                foreach ($need as $k => $v) {
                    if (Util::hasString($v, "as")) {
                        $str .= "$v,";
                    } else {
                        $str .= "`$v`,";
                    }
                }
                $str = substr($str, 0, -1);
                return $str;
            }
        } else {
            return '*';
        }
    }

    static protected function makeNeedV1(&$need)
    {
        $index = 0;
        $str = "";
        foreach ($need as $k => $v) {
            $index++;
            if (Util::hasString($v, "as")) {
                $str .= "$v,";
            } else {
                $str .= "`$v`,";
            }
        }

        $str = $str? substr($str, 0, -1):"*";

        return $str;
    }

    /**
     * @param $arr
     * @return object
     */
    abstract public function transaction($arr);

    /**
     * @param string $sql
     * @param array $data
     * @return mixed
     */
    abstract public function getObjArrBySql(string $sql, $data=[]);

    /**
     * @param string $sql
     * @param array $data
     * @return mixed
     */
    abstract public function getObjBySql(string $sql, $data=[]);

    /**
     * @param $dbName
     * @param array $data
     * @param array $where
     * @return mixed
     */
    abstract public function updateObj($dbName, $data = [], $where = []);

    /**
     * @param $dbName
     * @param array $where
     * @return mixed
     */
    abstract public function removeObj($dbName, $where = []);

    /**
     * @param $dbName
     * @param array $data
     * @return mixed
     */
    abstract public function replaceObj($dbName, $data = []);

    /**
     * @param $dbName
     * @param array $data
     * @return mixed
     */
    abstract public function addObj($dbName, $data = []);

    /**
     * @param $dbName
     * @param array $where
     * @return mixed
     */
    abstract public function getObjCount($dbName, $where = []);

    /**
     * @param $dbName
     * @param array $need
     * @param array $where
     * @param string $group
     * @param string $sort
     * @param string $limit
     * @return mixed
     */
    abstract public function getObjArr($dbName, $need = [], $where = [], $group = '', $sort = '', $limit = '');
    abstract public function getObjArrV1(&$rows, string $dbName, $select, array $where = [], string $group = '', $sort = '', array $limit = []);

    /**
     * @param $dbName
     * @param array $need
     * @param array $where
     * @param string $group
     * @param string $sort
     * @return mixed
     */
    abstract public function getObj($dbName, $need = [], $where = [], $group = '', $sort = '');
    abstract public function getObjV1(&$row, $dbName, $select, array $where = [], $group = '', $sort = '');

    /**
     * @param string $_sql
     * @return mixed
     */
    abstract public function execute(string $_sql);

    abstract public function getLastErr();
    abstract public function getLastSql();
    abstract public function getLastId();
    abstract public function getLastErrInfo(): string;
    abstract public function getLastErrCode(): string;
    abstract public function getLastParams(): array ;

    abstract public function setLastSql($sql);
    abstract public function setLastId($id);
    abstract public function setLastParams($params);
    abstract public function setLastErrInfo(string $info);
    abstract public function setLastErrCode($code);
}
